import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { WorldMap } from '@/components/map/world-map'; import { Block, DL, Empty, Head, Row } from '@/components/satellite/primitives'; import { OrbitBadge, StatusBadge, TypeBadge } from '@/components/ui/badges'; import { Container, Stat } from '@/components/ui/section'; import { api, ApiError } from '@/lib/api'; import { fmtDate, fmtDateTime, fmtDeg, fmtInt, num, titleCase } from '@/lib/format'; import { EVENT_TYPE_LABELS, OBJECT_TYPE_LABELS, routes, SITE_URL } from '@/lib/site'; import type { LaunchDetail } from '@/lib/types'; type Params = { params: Promise<{ cospar: string }> }; const TYPE_ORDER = ['PAYLOAD', 'STATION', 'CREWED', 'ROCKET_BODY', 'DEBRIS', 'UNKNOWN']; async function load(cospar: string): Promise { try { return (await api.launch(cospar)).data; } catch (e) { if (e instanceof ApiError && e.notFound) notFound(); throw e; } } function describe(l: LaunchDetail): string { return `Launch ${l.cospar_launch_id}${l.primary_name ? ` (${l.primary_name})` : ''} on ${fmtDate(l.launch_date)}${l.site_name ? ` from ${l.site_name}` : ''}: ${fmtInt(l.payload_count)} payloads, ${fmtInt(l.object_count)} catalogued objects, ${fmtInt(l.on_orbit_count)} still on orbit. Every object with its status, orbit and owner on SatelliteIndex.`; } export async function generateMetadata({ params }: Params): Promise { const { cospar } = await params; let l: LaunchDetail; try { l = (await api.launch(cospar)).data; } catch { return { title: 'Launch', robots: { index: false } }; } const title = `${l.primary_name ?? 'Launch'} — Launch ${l.cospar_launch_id}, ${fmtDate(l.launch_date)}`; const description = describe(l); const canonical = routes.launch(l.cospar_launch_id); return { title, description, alternates: { canonical }, openGraph: { title, description, url: `${SITE_URL}${canonical}`, type: 'article' }, twitter: { card: 'summary', title, description } }; } function OwnerChips({ owners }: { owners: LaunchDetail['owners'] }) { return ( ); } export default async function LaunchPage({ params }: Params) { const { cospar } = await params; const l = await load(cospar); const objects = num(l.object_count) ?? l.objects.length; const onOrbit = num(l.on_orbit_count) ?? 0; const groups = TYPE_ORDER.map((t) => ({ type: t, rows: l.objects.filter((o) => o.object_type === t) })).filter((g) => g.rows.length); const hasSite = l.site_lat != null && l.site_lon != null; const jsonLd = { '@context': 'https://schema.org', '@type': 'Event', name: `Launch ${l.cospar_launch_id}${l.primary_name ? ` — ${l.primary_name}` : ''}`, startDate: l.launch_date, url: `${SITE_URL}${routes.launch(l.cospar_launch_id)}`, description: describe(l), location: l.site_name ? { '@type': 'Place', name: l.site_name, ...(hasSite ? { geo: { '@type': 'GeoCoordinates', latitude: l.site_lat, longitude: l.site_lon } } : {}) } : undefined, identifier: { '@type': 'PropertyValue', propertyID: 'COSPAR launch id', value: l.cospar_launch_id } }; return (